#ifdef WIN32
#include <winsock2.h>
#endif

#include <iostream>
#include <vector>
#include <string>
#include "miniutil/strutil.h"
#include "miniutil/address.h"
#include "upnpcommands.h"
#include "miniupnpc.h"

char* usage = 
	"Very Small UPNP controller\n"
	"\tfind   find the available UPNP device\n"
	"\tlist   list all the portmapping information of [index] device\n"
	"\tadd export innport   add one port mapping entry with inport export\n"
	"\tdel [entry index]  -1 mean delete all\n"
	"\tquit quit application\n"
	"\n\n";


using namespace miniutil::upnp;

class supnpctroller
{
class mappingentry
{
public:
	std::string extPort;
	std::string intClient; 
	std::string intPort;
	std::string protocol;
	std::string desc;
	std::string enabled;
	std::string rHost;
	std::string duration;
};

public:
	supnpctroller()
	{
		allavailabledevices = NULL;
		availableigd.controlURL = NULL;
		availableigd.ipcondescURL = NULL;
		availableigd.controlURL_CIF = NULL;
	}

	~supnpctroller()
	{
		if(allavailabledevices != NULL)
		{
			freeUPNPDevlist(allavailabledevices);
			allavailabledevices = NULL;
		}
		FreeUPNPUrls(&availableigd);
	}

	UPNPDev * allavailabledevices;
	UPNPUrls  availableigd;
	std::vector<mappingentry> allentries;

	static char* mappname;
	static char* servicetype;
	static char* devicetype;

	std::string myaddrip;
	
	char externalip[64];
public:
	int FindIGD()
	{
		if(allavailabledevices != NULL)
		{
			freeUPNPDevlist(allavailabledevices);
			allavailabledevices = NULL;
		}

		int ret = -1;
        struct UPNPDev * devlist = NULL;
		char lanaddr[16]; /* my ip address on the LAN */
        memset(lanaddr, 0x00, sizeof(lanaddr));
		devlist = upnpDiscover(2000, NULL, NULL);
        if(devlist != NULL)
        {
            struct IGDdatas data;

			FreeUPNPUrls(&availableigd);
			int findret = UPNP_GetValidIGD(devlist, &availableigd, &data, lanaddr, sizeof(lanaddr));
			if(findret != 0)
			{
				//find ok
				ret = 0;
			}
			allavailabledevices = devlist;
		}
		return ret;
	}

	int ListEntry()
	{
		if(allavailabledevices == NULL)
			return -1;
		if(availableigd.controlURL == NULL)
			return -1;

		allentries.clear();

		int ret = -1;
		char index[10];
		char extPort[32], intClient[128], intPort[32], protocol[512],
			 desc[512], enabled[32], rHost[128], duration[64];

		memset(index, 0x00, sizeof(index));
		memset(extPort, 0x00, sizeof(extPort));
		memset(intClient, 0x00, sizeof(extPort));
		memset(intPort, 0x00, sizeof(extPort));
		memset(desc, 0x00, sizeof(extPort));
		memset(enabled, 0x00, sizeof(extPort));
		memset(rHost, 0x00, sizeof(extPort));
		memset(duration, 0x00, sizeof(extPort));

		int i=0;
		while(true)
		{
			sprintf((char*)index, "%d", i);
			int ret = miniutil::upnp::UPNP_GetGenericPortMappingEntry(availableigd.controlURL,servicetype,(char*)index,
				extPort, intClient, intPort, protocol, desc, enabled, rHost, duration);
			if(ret == UPNPCOMMAND_SUCCESS)
			{
				mappingentry entry;
				entry.extPort = extPort;
				entry.intClient = intClient;
				entry.intPort = intPort;
				entry.protocol = protocol;
				entry.desc = desc;
				entry.enabled = enabled;
				entry.rHost = rHost;
				entry.duration = duration;
				allentries.push_back(entry);
			}
			else
			{
				std::cout<<index<<" operation Fail"<<std::endl;
				break;
			}
			i++;
		}
		if(allentries.size() > 0)
			return 0;
		return -1;
	}

	int DelEntry(int idx)
	{
		int ret;
		if(allavailabledevices == NULL)
			return -1;
		if(availableigd.controlURL == NULL)
			return -1;

		if(idx < 0)
		{
			for(int i=0; i< allentries.size(); i++)
			{
				ret = miniutil::upnp::UPNP_DeletePortMapping(availableigd.controlURL,servicetype,
					allentries[i].extPort.c_str(), allentries[i].protocol.c_str());
			}
			allentries.clear();
		}

		if(idx < allentries.size())
		{
			int ret = miniutil::upnp::UPNP_DeletePortMapping(availableigd.controlURL,servicetype,
                               allentries[idx].extPort.c_str(), allentries[idx].protocol.c_str());
			if(ret == UPNPCOMMAND_SUCCESS)
			{
				allentries.erase(allentries.begin()+idx);
			}
		}
		return 0;
	}

	int AddEntry(int internalPort, int externalPort, std::string proto = "TCP")
	{
 		if(allavailabledevices == NULL)
			return -1;
		if(availableigd.controlURL == NULL)
			return -1;

		if(myaddrip.empty())
		{
			myaddrip = miniutil::ip4addr::MYSELF().IP();
		}

		int ret;

		ret = miniutil::upnp::UPNP_AddPortMapping(availableigd.controlURL,servicetype,
            miniutil::util::toString(externalPort).c_str() , 
            miniutil::util::toString(internalPort).c_str(), 
			myaddrip.c_str(), mappname, proto.c_str());

        if(ret != UPNPCOMMAND_SUCCESS)
            return -1 ;
        //ret = miniutil::upnp::UPNP_AddPortMapping(availableigd.controlURL,servicetype,
        //    miniutil::util::toString(externalPort).c_str(), 
        //    miniutil::util::toString(internalPort).c_str(), 
        //    lanaddr, mappname, "UDP");

        //if(ret != UPNPCOMMAND_SUCCESS)
        //{
        //    UPNP_DeletePortMapping(availableigd.controlURL, servicetype,  
        //        miniutil::util::toString( externalPort ).c_str(), "TCP");
        //    return -1 ;
        //}
		return 0;
	}

	char* GetExternalIp()
	{
 		if(allavailabledevices == NULL)
			return NULL;
		if(availableigd.controlURL == NULL)
			return NULL;
		int ret = UPNP_GetExternalIPAddress(availableigd.controlURL,servicetype, externalip);
		if(ret == UPNPCOMMAND_SUCCESS)
		{
			return externalip;
		}
		return NULL;

	}
};

char* supnpctroller::mappname = "supnpctroller";
char* supnpctroller::servicetype = "urn:schemas-upnp-org:service:WANIPConnection:1";
char* supnpctroller::devicetype = "urn:schemas-upnp-org:device:InternetGatewayDevice:1";

int main()
{
#ifdef WIN32
	{WSADATA data; WSAStartup(MAKEWORD(2,2), &data);}
#endif
	supnpctroller actrl;
	char cmdline[1024];
	std::cout<<usage;
	int ret;
	while(true)
	{
		memset(cmdline,0x00, sizeof(cmdline));
		std::cin.getline(cmdline, 1024);
		std::vector<std::string> cmds = miniutil::strutil::split(cmdline, " ");
		if(cmds.size() == 0)
			continue;

		if(cmds[0] == "find")
		{
			ret = actrl.FindIGD();
			if(ret == 0)
			{
				std::cout<<"find operation OK------------------------"<<std::endl;
				UPNPDev *device = NULL;
				std::cout<<"Devices"<<std::endl;
				for(device = actrl.allavailabledevices; device; device = device->pNext)
                {
					std::cout<<" desc: "<<device->descURL<<" st: "<<device->st<<"\n";
                }
				std::cout<<"IGDs"<<std::endl;
				std::cout<<actrl.availableigd.controlURL<<"\n"
					<<actrl.availableigd.ipcondescURL<<"\n"<<actrl.availableigd.controlURL_CIF<<"\n";
			}
			else
			{
				std::cout<<"find operation fail------------------------"<<std::endl;
			}
		}
		else if(cmds[0] == "list")
		{
			std::cout
				<<"{extPort}{intClient}{intPort}{protocol}{desc}{enabled}{rHost}{duration}"<<std::endl;
			ret = actrl.ListEntry();
			if(ret == 0)
			{
				std::cout<<"list operation OK------------------------"<<std::endl;
				for(int i=0; i< actrl.allentries.size(); i++)
				std::cout
					<<"{"<<actrl.allentries[i].extPort
					<<"}{"<<actrl.allentries[i].intClient<<"}{"<<actrl.allentries[i].intPort
					<<"}{"<<actrl.allentries[i].protocol
					<<"}{"<<actrl.allentries[i].desc<<"}{"<<actrl.allentries[i].enabled
					<<"}{"<<actrl.allentries[i].rHost<<"}{"<<actrl.allentries[i].duration<<"}"<<std::endl;
			}
			else
			{
				std::cout<<"no entry available------------------------"<<std::endl;
			}
		}
		else if(cmds[0] == "add")
		{
			if(cmds.size() == 4)
				ret = actrl.AddEntry(atoi(cmds[2].c_str()), atoi(cmds[1].c_str()), cmds[3]);
			else if(cmds.size() == 3)
				ret = actrl.AddEntry(atoi(cmds[2].c_str()), atoi(cmds[1].c_str()));
			if(ret == 0)
			{
				std::cout<<"add operation OK------------------------"<<std::endl;
			}
			else
			{
				std::cout<<"add operation fail------------------------"<<std::endl;
			}
		}
		else if(cmds[0] == "del")
		{
			if(cmds.size() == 2)
				ret = actrl.DelEntry(atoi(cmds[1].c_str()));
			if(ret == 0)
			{
				std::cout<<"del operation OK------------------------"<<std::endl;
			}
			else
			{
				std::cout<<"del operation fail------------------------"<<std::endl;
			}
		}
		else if(cmds[0] == "ip")
		{
			char* pip = actrl.GetExternalIp();
			if(pip != NULL)
			{
				std::cout<<"external IP:"<<pip<<std::endl;
			}
			else
			{
				std::cout<<"external IP not exist"<<std::endl;
			}
		}
		else if(cmds[0] == "quit")
		{
			break;
		}
		else
		{
			std::cout<<"unknow command!!"<<std::endl;
		}
	}
}